package com.atguigu.tingshu.user.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.login.GuiguLogin;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.model.user.UserPaidAlbum;
import com.atguigu.tingshu.model.user.UserPaidTrack;
import com.atguigu.tingshu.user.config.WxLonginPropertiesConfig;
import com.atguigu.tingshu.user.mapper.UserInfoMapper;
import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
import com.atguigu.tingshu.user.service.UserInfoService;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.security.interfaces.RSAPrivateKey;
import java.util.Base64;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {

    private final UserInfoMapper userInfoMapper;
    private final WxLonginPropertiesConfig wxLonginPropertiesConfig;
    private final RestTemplate restTemplate;
    private final RSAPrivateKey rsaPrivateKeyInit;
    private final RedisTemplate redisTemplate;
    private final RabbitTemplate rabbitTemplate;
    private final UserPaidAlbumMapper userPaidAlbumMapper;
    private final UserPaidTrackMapper userPaidTrackMapper;


    @Autowired
    public UserInfoServiceImpl(UserInfoMapper userInfoMapper, WxLonginPropertiesConfig wxLonginPropertiesConfig, RestTemplate restTemplate, RSAPrivateKey rsaPrivateKeyInit, RedisTemplate redisTemplate, RabbitTemplate rabbitTemplate, UserPaidAlbumMapper userPaidAlbumMapper, UserPaidTrackMapper userPaidTrackMapper) {
        this.userInfoMapper = userInfoMapper;
        this.wxLonginPropertiesConfig = wxLonginPropertiesConfig;
        this.restTemplate = restTemplate;
        this.rsaPrivateKeyInit = rsaPrivateKeyInit;
        this.redisTemplate = redisTemplate;
        this.rabbitTemplate = rabbitTemplate;
        this.userPaidAlbumMapper = userPaidAlbumMapper;
        this.userPaidTrackMapper = userPaidTrackMapper;
    }

    public static void main(String[] args) {
        byte[] a = Base64.getUrlDecoder().decode("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9");
        System.out.println(new String(a));
        a = Base64.getUrlDecoder().decode("eyJhdmF0YXJVcmwiOiIiLCJjcmVhdGVUaW1lIjoxNzQ1NjY2NjE2MDAwLCJpZCI6MjUsImlzRGVsZXRlZCI6MCwiaXNWaXAiOjAsIm5pY2tuYW1lIjoi5b6u5L-h55m75b2V55So5oi3IiwicGFyYW0iOnt9LCJzdGF0dXMiOiIwIiwidXBkYXRlVGltZSI6MTc0NTY2NjYxNjAwMCwid3hPcGVuSWQiOiJvM3ZXUzVJU0djVm05RDF2NUJIcmdHS1RSWFYwIn0");
        System.out.println(new String(a));
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        for (int i = 0; i < 10; i++) {
            // 加密密文（每次都不一样）
            String encode = encoder.encode("123456");
            System.out.println(encode);
            // 明文匹配密文
            System.out.println(encoder.matches("123456", encode));

            // 也就是说使用私钥公钥加密一个共同的明文，加密结果是不一样的，但是他们之间是可以匹配的（公钥只和私钥匹配）
            // 也就是说服务端登录模块使用私钥加密用户的登录信息生成一个token，当校验模块接收到token时可以使用公钥根据信息重新生成一个token，然后进行匹配，校验是否是同一个用户
        }
    }

    /*
    1. 秘钥：生成自己的公钥和私钥
    2. 私钥：在登录的时候生成token
    3. 公钥：校验的时候使用校验token
     */

    /*
    jwt：json web token
    组成部分：
        头：算法、类型 --> base64 加密之后的json字符串
        载荷：自定义的json字符串数据
        签名：
            生成规则：头里面的定义的算法（Bese64（头）+ Base64（载荷）+盐（私钥）） --> 签名
            作用：签名是校验一个token是否合法的唯一标识
            特点：每次生成的签名都不一样，但是校验（公钥）是匹配校验不是相等校验
     */

    /**
     * 微信小程序登录
     * 问题1：多端登录的控制：
     * 1. 用户名+密码的组合在发放令牌前限制多端
     * 2. code授权码这种模式确认身份后才能将旧的删掉保留新的
     *
     * @param code
     * @return
     */
    @Override
    public Object wxLogin(String code) {
        // 参数校验
        if (StringUtils.isEmpty(code)) {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }
        // 请求微信服务器获取用户的唯一标识
        // 获取appId
        String appId = wxLonginPropertiesConfig.getAppId();
        // 获取appSecret
        String appSecret = wxLonginPropertiesConfig.getAppSecret();
        // 获取微信认证请求地址（包含了固定参数）：https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code
        String url = wxLonginPropertiesConfig.getUrl();
        // 拼接请求参数
        url += "&appid=" + appId + "&secret=" + appSecret + "&js_code=" + code;
        // 发起请求
        ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
        // 获取唯一标识
        JSONObject body = JSONObject.parseObject(response.getBody());
        String openid;
        if (body != null) {
            openid = body.getString("openid");
        } else {
            throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
        }

        // 判断用户是否为第一次登录
        UserInfo userInfo = userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getWxOpenId, openid));
        if (userInfo == null) {
            // 第一次需要初始化一些信息
            userInfo = new UserInfo();
            userInfo.setWxOpenId(openid);
            userInfo.setNickname("微信登录用户");
            userInfo.setIsVip(0);
            userInfo.setVipExpireTime(new Date());
            int insert = userInfoMapper.insert(userInfo);
            if (insert <= 0) {
                throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
            }
            // 第一次登录创建账户的信息（记录消费余额等等信息）
            // 1. openfeign：内部http调用 -- 同步（后续操作需要等待）
            // 2. 异步（针对用户来说的比同步效率高）：
            //      a. Java原生的：异步编排/异步请求/异步方法
            //          CompletableFuture.runAsync(() -> {
            //             // 初始化账户
            //          });
            //      b. 消息队列：异步、削峰、解耦、排队
            /*
                两种消息队列种类（非产品）：JMS（Java Message Service）、AMQP（网络协议/通信协议）
                    JMS：生产者/消费者/消息队列 都必须运行在java平台，即不跨语言不跨平台 -- RocketMQ
                    AMQP：数据格式只有bate[] 跨语言跨平台 -- RabbitMQ、Kafka（需要安装zookeeper注册中心-隶属于dubbo微服务框架（alibaba已经放弃）-rpc协议）
             */
            // 消息一律发字符串
            rabbitTemplate.convertAndSend("account_exchange", "user.init", userInfo.getId().toString());
        } else {
            // 设备号校验：前端需要传递本次登录的设备号参数 --> 和数据库中的设备号进行对比

            // 如果一致，什么都不做，发放令牌

            // 如果不一致，提示用户更换了设备 --> 验证一下手机号

        }
        // 不是第一次则直接发放令牌：token
        JSONObject data = new JSONObject();
        data.put("userId", userInfo.getId());
        data.put("isVip", userInfo.getIsVip());
        data.put("vipExpireTime", userInfo.getVipExpireTime().getTime());
        Jwt encode = JwtHelper.encode(data.toJSONString(), new RsaSigner(rsaPrivateKeyInit));
        // 获取jwt类型的token
        String token = encode.getEncoded();
        JSONObject result = new JSONObject();
        result.put("token", token);
        // 存储用户的登录的信息（token/设备号等信息）
        redisTemplate.opsForValue().set("User_Login_" + userInfo.getId(), "123", 2, TimeUnit.HOURS);
        // 返回
        return result;
    }

    /**
     * 查询已登录用户的信息
     *
     * @return 用户信息
     */
    @GuiguLogin
    @Override
    public UserInfoVo getUserInfo() {
        // 从本地线程类获取userId
        Long userId = AuthContextHolder.getUserId();
        // 根据id查询用户信息
        UserInfo userInfo = userInfoMapper.selectById(userId);
        // 返回结果初始化
        UserInfoVo userInfoVo = new UserInfoVo();
        BeanUtils.copyProperties(userInfo, userInfoVo);
        // 返回
        return userInfoVo;
    }

    /**
     * 查询用户购买专辑的状态
     *
     * @param userId
     * @param albumId
     * @return
     */
    @Override
    public Boolean getUserAlbumPaidStatus(Long userId, Long albumId) {
        return userPaidAlbumMapper.selectCount(new LambdaQueryWrapper<UserPaidAlbum>().eq(UserPaidAlbum::getUserId, userId).eq(UserPaidAlbum::getAlbumId, albumId)).equals(1L);
    }

    /**
     * 查询用户购买的专辑的声音map集合
     *
     * @param userId
     * @param albumId
     * @return
     */
    @Override
    public Map<String, Object> getUserTrackPaidMap(Long userId, Long albumId) {
        return userPaidTrackMapper.selectList(new LambdaQueryWrapper<UserPaidTrack>().eq(UserPaidTrack::getAlbumId, albumId).eq(UserPaidTrack::getUserId, userId)).stream().collect(Collectors.toMap(
            key -> key.getTrackId().toString(),
            value -> value.getCreateTime().getTime()
        ));
    }
}
